package com.github.baichuan.iot.gateway;

import io.netty.bootstrap.AbstractBootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;

/**
 * 网关启动类
 */
@Slf4j
public class GatewayBootstrap {
    private Network network;

    private ChannelInitializer<? extends Channel> channelInitializer;

    private Map<ChannelOption<?>, Object> options = new HashMap<>();

    private Map<ChannelOption<?>, Object> childOptions = new HashMap<>();

    private SocketAddress bindAddress;

    public GatewayBootstrap network(Network network){
        this.network = network;
        return this;
    }

    public GatewayBootstrap channelInitializer(ChannelInitializer<? extends Channel> channelInitializer){
        this.channelInitializer = channelInitializer;
        return this;
    }

    public <T> GatewayBootstrap option(ChannelOption<T> option, T value){
        options.put(option, value);
        return this;
    }

    public <T> GatewayBootstrap childOption(ChannelOption<T> option, T value){
        childOptions.put(option, value);
        return this;
    }

    public GatewayBootstrap bind(SocketAddress address){
        this.bindAddress = address;
        return this;
    }

    public void start() throws InterruptedException {
        check();

        log.info("gateway network protocol is {}, bind address {}", network.toString(), bindAddress.toString());

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try{
            AbstractBootstrap bootstrap = bootstrap(bossGroup, workGroup);
            for(Map.Entry<ChannelOption<?>, Object> entry : options.entrySet()){
                bootstrap.option(entry.getKey(), entry.getValue());
            }

            ChannelFuture future = bootstrap.bind(bindAddress).sync();
            future.channel().closeFuture().sync();
        }
        finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    private AbstractBootstrap bootstrap(EventLoopGroup bossGroup, EventLoopGroup workGourp){
        if(network == Network.UDP){
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(bossGroup)
                    .channel(NioDatagramChannel.class)
                    .handler(channelInitializer);
            return bootstrap;
        }
        else{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGourp)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(channelInitializer);
            for(Map.Entry<ChannelOption<?>, Object> entry : childOptions.entrySet()){
                bootstrap.childOption((ChannelOption<? super Object>) entry.getKey(), entry.getValue());
            }
            return bootstrap;
        }
    }
    private void check(){
        if(network == null){
            throw new IllegalStateException("gateway network is null");
        }

        if(bindAddress == null){
            throw new IllegalStateException("bindAddresses is null");
        }

        if(channelInitializer == null){
            throw new IllegalStateException("channelInitializer is null");
        }
    }

}
